package com.soccertgc.flex.data 
{
	import com.soccertgc.Application;
	import com.soccertgc.Shared;
	import com.soccertgc.core.DataObject;
	import com.soccertgc.core.DispatcherDataObject;
	import com.soccertgc.core.cards.ActionCard;
	import com.soccertgc.core.cards.Card;
	import com.soccertgc.core.cards.CardFactory;
	import com.soccertgc.core.cards.CardType;
	import com.soccertgc.core.cards.PlayerCard;
	import com.soccertgc.core.cards.TrainingCard;
	import com.soccertgc.logging.Dumper;
	import com.soccertgc.logging.IDumpable;
	
	import flash.events.Event;
	
	import mx.collections.ArrayCollection;
	import mx.collections.ArrayList;
	import mx.collections.IList;
	
	
	public class FlexCollection extends DispatcherDataObject implements IDumpable
	{
		
		// -------------------------------------------------------------------------------
		// CONSTANTS
		// -------------------------------------------------------------------------------

		// JSON Property names
		public static const 	JSON_PROPERTY_CARD_CASE:String				= "cardCase";
		
		// Events
		public static const		EVENT_ACTIVE_DECK_CHANGED:String 			= "active_deck_changed";
		public static const		EVENT_DECK_LIST_CHANGED:String 				= "deck_list_changed";
		public static const		EVENT_CARD_LIST_CHANGED:String 				= "card_list_changed";
		public static const		EVENT_ACTION_CARD_LIST_CHANGED:String 		= "action_card_list_changed";
		public static const		EVENT_PLAYER_CARD_LIST_CHANGED:String 		= "player_card_list_changed";
		public static const		EVENT_TRAINING_CARD_LIST_CHANGED:String 	= "training_card_list_changed";
		
		// -------------------------------------------------------------------------------
		// FIELDS
		// -------------------------------------------------------------------------------
		
		private var _activeDeckId:int; 
		private var _cards:ArrayCollection;
		private var _actionCards:ArrayCollection;
		private var _playerCards:ArrayCollection;
		private var _trainingCards:ArrayCollection;
		private var _decks:ArrayCollection;
		
		// -------------------------------------------------------------------------------
		// CONSTRUCTOR
		// -------------------------------------------------------------------------------
		
		public function FlexCollection()
		{
			_activeDeckId = DataObject.INVALID_ID;
			_cards = new ArrayCollection();
			_actionCards = new ArrayCollection();
			_playerCards = new ArrayCollection();
			_trainingCards = new ArrayCollection();
			_decks = new ArrayCollection();
		}
		
		// -------------------------------------------------------------------------------
		// PROPERTIES
		// -------------------------------------------------------------------------------
		
		[Bindable(event="active_deck_changed")]
		public function get activeDeck():FlexDeck
		{
			return hasActiveDeck ? getDeckById(_activeDeckId) : null;
		}
		
		public function set activeDeck(value:FlexDeck):void
		{
			// Check if the Deck is valid
			if (!value.isActivable) throw new Error("Deck coudn't be set as active deck");
			// check if the deck is contained into the collection
			if (!containsDeck(value)) throw new Error("Deck not contained into the collection! Coudn't be set as active deck");
			// Sets the active deck id
			_activeDeckId = value.id;
		}
		
		public function get hasActiveDeck():Boolean
		{
			return _activeDeckId != DataObject.INVALID_ID;
		}
		
		[Bindable(event="deck_list_changed")]
		public function get decksCount():int
		{
			return _decks.source.length;
		}
		
		[Bindable(event="deck_list_changed")]
		public function get decks():ArrayCollection
		{
			return _decks;
		}
		
		[Bindable(event="card_list_changed")]
		public function get cards():ArrayCollection
		{
			return _cards;
		}
		
		[Bindable(event="card_list_changed")]
		public function get cardCount():int
		{
			return _cards.source.length;
		}
		
		[Bindable(event="player_card_list_changed")]
		public function get playerCards():ArrayCollection
		{
			return _playerCards;
		}
		
		[Bindable(event="player_card_list_changed")]
		public function get playerCardsCount():int
		{
			return _playerCards.length;
		}
		
		[Bindable(event="action_card_list_changed")]
		public function get actionCards():ArrayCollection
		{
			return _actionCards;
		}
		
		[Bindable(event="action_card_list_changed")]
		public function get actionCardsCount():int
		{
			return _actionCards.length;
		}
		
		[Bindable(event="training_card_list_changed")]
		public function get trainingCards():ArrayCollection
		{
			return _trainingCards;
		}
		
		[Bindable(event="training_card_list_changed")]
		public function get trainingCardsCount():int
		{
			return _trainingCards.length;
		}
		
		// -------------------------------------------------------------------------------
		// PRIVATE METHODS
		// -------------------------------------------------------------------------------
		
		protected function getCardIndexByInstanceId(instanceId:uint):int
		{
			for (var i:int; i<_cards.source.length; i++)
			{
				var card:Card = _cards.source[i] as Card;
				if (card.instanceId == instanceId) return i;
			}
			return Shared.INVALID_INDEX;
		}

		protected function getCardsByType(type:CardType):ArrayCollection
		{
			var cards:ArrayCollection = new ArrayCollection();
			for (var i:int = 0; i<_cards.source.length; i++)
			{
				var card:Card = _cards.source[i] as Card;
				if (card.type == type)
				{
					cards.addItem(card);
				}
			}
			return cards;
		}

		protected function getDeckIndexById(id:uint):int
		{
			for (var i:int=0;i<_decks.source.length; i++)
			{
				var deck:FlexDeck = _decks.source[i] as FlexDeck;
				if (deck.id == id) return i;
			}
			return Shared.INVALID_INDEX;
		}
		
		protected function getDeckIndexByName(name:String):int
		{
			for (var i:int=0;i<_decks.source.length; i++)
			{
				var deck:FlexDeck = _decks.source[i] as FlexDeck;
				if (deck.name.toLowerCase() == name.toLowerCase()) return i;
			}
			return Shared.INVALID_INDEX;
		}

		protected function getActionCardIndexByInstanceId(instanceId:uint):int
		{
			for (var i:int; i<_actionCards.source.length; i++)
			{
				var card:Card = _actionCards.source[i] as Card;
				if (card.instanceId == instanceId) return i;
			}
			return Shared.INVALID_INDEX;
		}
		
		protected function getPlayerCardIndexByInstanceId(instanceId:uint):int
		{
			for (var i:int; i<_playerCards.source.length; i++)
			{
				var card:Card = _playerCards.source[i] as Card;
				if (card.instanceId == instanceId) return i;
			}
			return Shared.INVALID_INDEX;
		}
		
		protected function getTrainingCardIndexByInstanceId(instanceId:uint):int
		{
			for (var i:int; i<_trainingCards.source.length; i++)
			{
				var card:Card = _trainingCards.source[i] as Card;
				if (card.instanceId == instanceId) return i;
			}
			return Shared.INVALID_INDEX;
		}
		
		protected function addAllCards(cards:IList):void
		{
			for (var i:int; i<cards.length; i++)
			{
				var card:Card = cards.getItemAt(i) as Card;
				// Add the card to the full list
				_cards.addItem(card);
				// Add the card to the typed list and dispatch custom card type events
				switch(card.type)
				{
					case CardType.ACTION:		_actionCards.addItem(card); 	break;
					case CardType.PLAYER:		
					case CardType.GOALKEEPER:	_playerCards.addItem(card);		break;
					case CardType.TRAINING:		_trainingCards.addItem(card);	break;
				}
			}
		}
		
		protected function equalsCards(collection:FlexCollection):Boolean
		{
			if (cardCount != collection.cardCount) return false;
			for (var i:int = 0; i < cardCount; i++)
			{
				var c1:Card = getCard(i);
				var c2:Card = collection.getCard(i);
				if (!c1.equals(c2)) return false;
			}
			return true;
		}
		
		protected function equalsDecks(collection:FlexCollection):Boolean
		{
			if (decksCount != collection.decksCount) return false;
			for (var i:int = 0; i < decksCount; i++)
			{
				var d1:FlexDeck = getDeck(i);
				var d2:FlexDeck = collection.getDeck(i);
				if (!d1.equals(d2)) return false;
			}
			return true;
		}
		
		// DATA OBJECT IMPLEMENTATION
		
		override protected function validate(data:Object):void
		{
			if (data == null)									throwLoadPropNullError();
			if (!data.hasOwnProperty(JSON_PROPERTY_CARD_CASE))	throwLoadMissingPropError("card case");
		}
		
		override protected function fill(data:Object):void
		{
			var raw_cards:Array = data[JSON_PROPERTY_CARD_CASE];
			var cards:Vector.<Card> = new Vector.<Card>;
			// Card data parsing
			for (var i:int=0; i<raw_cards.length; i++)
			{
				// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
				// TODO: skip cards of type 99!! temporary patch to remove!
				if (raw_cards[i].type == 99) continue;
				// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
				// Retrieve card type
				var type:CardType = CardType.getByObject(raw_cards[i]);
				
				
				if (!Application.instance.config.useOldActionCardParsing && type == CardType.ACTION)
				{
					var actions:Vector.<ActionCard> = CardFactory.createActionCards(raw_cards[i]);
					for (var k:int = 0; k < actions.length; k++)
					{
						cards.push(actions[k]);
					}
				}
				else
				{
					// Creates a new card 
					cards.push(CardFactory.create(type, raw_cards[i]));			
				}

			}
			// Clears card list
			clearCards();
			// Popolate the collection
			for (var j:int = 0; j<cards.length; j++)
			{
				_cards.addItem(cards[j]);
			}
			// Dispatch custom EVENT_CARD_LIST_CHANGED event to trigger databinding
			dispatchCardListChanged();
			// Dispatch custom typed card list event to trigger databinding
			dispatchActionCardListChanged();
			dispatchPlayerCardListChanged();
			dispatchTrainingCardListChanged();
		}
		
		// EVENT DISPATCHERS
		
		protected function dispatchActiveDeckChanged():void
		{
			dispatchEvent(new Event(EVENT_ACTIVE_DECK_CHANGED));
		}
		
		protected function dispatchDeckListChanged():void
		{
			dispatchEvent(new Event(EVENT_DECK_LIST_CHANGED));
		}
		
		protected function dispatchCardListChanged():void
		{
			dispatchEvent(new Event(EVENT_CARD_LIST_CHANGED));
		}
		
		protected function dispatchActionCardListChanged():void
		{
			dispatchEvent(new Event(EVENT_ACTION_CARD_LIST_CHANGED));
		}
		
		protected function dispatchPlayerCardListChanged():void
		{
			dispatchEvent(new Event(EVENT_PLAYER_CARD_LIST_CHANGED));
		}
		
		protected function dispatchTrainingCardListChanged():void
		{
			dispatchEvent(new Event(EVENT_TRAINING_CARD_LIST_CHANGED));
		}
		
		// -------------------------------------------------------------------------------
		// PUBLIC METHODS
		// -------------------------------------------------------------------------------
		
		/**
		 * Creates a new deck for the collection, already added to the collection
		 * @param id Deck unique id
		 * @param name Deck name
		 * @return 
		 */
		// public function createNewDeck(id:uint, name:String=Deck.DEFAULT_NAME, fillLeftCards:Boolean = false):Deck
		public function createNewDeck(id:uint, name:String , fillLeftCards:Boolean = false):FlexDeck
		{
			var deck:FlexDeck = createEmptyDeck(id, name);
			if (fillLeftCards) deck.updateLeftCards();
			addDeck(deck);
			return deck;
		}
		
		/**
		 * Creates an empty deck without any data inside and not added to the owner Collection
		 * @param id Deck unique id
		 * @param name Deck name
		 * @return Empty Deck linked to the current Collection 
		 */			
		public function createEmptyDeck(id:uint, name:String):FlexDeck
		{
			return new FlexDeck(id, name, this);
		}
		
		/**
		 * Retrieves a Card using his index in the list
		 * @param index Index of the Card to retrieve
		 * @return Card for the given index in the collection
		 */
		public function getCard(index:int):Card
		{
			return _cards.source[index] as Card;
		}
		
		/**
		 * Retrieves a Card using instance unique id
		 * @param instanceId Card instance unique id
		 * @return Card for the given instance unique id
		 */		
		public function getCardByInstanceId(instanceId:int):Card
		{
			return _cards.source[getCardIndexByInstanceId(instanceId)] as Card;
		}
		
		/**
		 * Returns all the instance of a card present into the collection 
		 * @param cardId Card id to search
		 * @return List of Card having the given cardId
		 */		
		public function getCardsByCardId(cardId:uint):ArrayList
		{
			var instances:ArrayList = new ArrayList();
			// Loop over the cards
			for (var i:int = 0; i<_cards.source.length; i++)
			{
				var card:Card = getCard(i);
				if (card.cardId == cardId)
				{
					instances.addItem(card);
				}
			}
			return instances;
		}
		
		/**
		 * Counts the number of instance for the same card by the card id
		 * @param cardId Card id
		 * @return Number of the number of instances for the same card
		 */		
		public function getCardInstancesCountByCardId(cardId:uint):int
		{
			var count:int = 0;
			for (var i:int; i<_cards.source.length; i++)
			{
				var c:Card = getCard(i);
				if (c.cardId == cardId) count++;
			}
			return count;
		}
		
		/**
		 * Counts the number of instance for the same card
		 * @param cardId Card id
		 * @return Number of the number of instances for the same card
		 */		
		public function getCardInstancesCount(card:Card):int
		{
			var count:int = 0;
			for (var i:int; i<_cards.length; i++)
			{
				var c:Card = _cards.getItemAt(i) as Card;
				if (c.cardId == card.cardId) count++;
			}
			return count;
		}
		
		/**
		 * Check if the Collection contains a card by his instance unique id
		 * @param instanceId Card instance unique id
		 * @return True if the Collection contains a Card for the given instance unique id, 
		 */		
		public function containsCardByInstanceId(instanceId:int):Boolean
		{
			return (getCardIndexByInstanceId(instanceId) > Shared.INVALID_INDEX);
		}
		
		/**
		 * Check if the Collection contains the given card, using his instance unique id
		 * @param card Card to check
		 * @return True if the Collection contains the given Card 
		 */
		public function containsCard(card:Card):Boolean
		{
			return (getCardIndexByInstanceId(card.instanceId) > Shared.INVALID_INDEX) ;
		}
		
		/**
		 * Retrieves an ActionCard using his index in the action card list
		 * @param index Index of the ActionCard to retrieve
		 * @return ActionCard for the given index in the action card list
		 */
		public function getActionCard(index:int):ActionCard
		{
			return _actionCards.source[index] as ActionCard;
		}
		
		/**
		 * Retrieves an PlayerCard using his index in the player card list
		 * @param index Index of the PlayerCard to retrieve
		 * @return PlayerCard for the given index in the player card list
		 */
		public function getPlayerCard(index:int):PlayerCard
		{
			return _playerCards.source[index] as PlayerCard;
		}
		
		/**
		 * Retrieves an TrainingCard using his index in the training card list
		 * @param index Index of the TrainingCard to retrieve
		 * @return TrainingCard for the given index in the training card list
		 */
		public function getTrainingCard(index:int):TrainingCard
		{
			return _trainingCards.source[index] as TrainingCard;
		}
		
		/**
		 * Adds a card to the collection
		 * @param card Card to add
		 */		
		public function addCard(card:Card):void
		{
			// Card duplication check
			if (containsCard(card)) throw new Error("Card '" + card.name + "' instance already contained into the collection!");
			// Add the card to the full list
			_cards.addItem(card);
			// Dispatch CARD_LIST_CHANGED event
			dispatchCardListChanged();
			// Add the card to the typed list and dispatch custom card type events
			switch(card.type)
			{
				case CardType.ACTION:
					_actionCards.addItem(card);
					dispatchActionCardListChanged();	
					break;
				case CardType.PLAYER:		
				case CardType.GOALKEEPER:	
					_playerCards.addItem(card);
					dispatchPlayerCardListChanged();	
					break;
				case CardType.TRAINING:
					_trainingCards.addItem(card);
					dispatchTrainingCardListChanged();	
					break;
			}
		}
		
		/**
		 * Remove a card from the collection
		 * @param card Card to remove
		 */		
		public function removeCard(card:Card):void
		{
			// Card duplication check
			if (!containsCard(card)) throw new Error("Card '" + card.name + "' instance not contained into the collection!");
			// remove the card
			_cards.removeItemAt(getCardIndexByInstanceId(card.instanceId));
			// Dispatch CARD_LIST_CHANGED event
			dispatchCardListChanged();
			// Dispatch custo card type events
			switch(card.type)
			{
				case CardType.ACTION:
					_actionCards.removeItemAt( getActionCardIndexByInstanceId(card.instanceId) );
					dispatchActionCardListChanged();
					break;
				case CardType.PLAYER:		
				case CardType.GOALKEEPER:
					_playerCards.removeItemAt( getPlayerCardIndexByInstanceId(card.instanceId) );
					dispatchPlayerCardListChanged();
					break;
				case CardType.TRAINING:
					_trainingCards.removeItemAt( getTrainingCardIndexByInstanceId(card.instanceId) );
					dispatchTrainingCardListChanged();
					break;
			}
		}
		
		/**
		 * Removes all the cards from the collection
		 */		
		public function clearCards():void
		{
			_cards.removeAll();
			_actionCards.removeAll();
			_playerCards.removeAll();
			_trainingCards.removeAll();
			// Dispatch CARD_LIST_CHANGED event
			dispatchCardListChanged();
			// Dispatch custom typed card list event to trigger databinding
			dispatchActionCardListChanged();
			dispatchPlayerCardListChanged();
			dispatchTrainingCardListChanged();
		}
		
		/**
		 * Verify if a Deck is already contained into the collection using is unique id
		 * @param id Deck unique id to verify
		 * @return True if the deck is already contained
		 */		
		public function containsDeckById(id:int):Boolean
		{
			return getDeckIndexById(id) != Shared.INVALID_INDEX;
		}
		
		/**
		 * Verify if a Deck is already contained into the collection using is name
		 * @param id Deck name to verify
		 * @return True if the deck is already contained
		 */		
		public function containsDeckByName(name:String):Boolean
		{
			return getDeckIndexByName(name) != Shared.INVALID_INDEX;
		}
		
		/**
		 * Verify if a Deck is already contained into the collection
		 * @param deck Deck to verify
		 * @return True if the deck is already contained
		 */		
		public function containsDeck(deck:FlexDeck):Boolean
		{
			return getDeckIndexById(deck.id) != Shared.INVALID_INDEX;
		}
		
		/**
		 * Retrieves a Deck using his index in the list
		 * @param index Index of the Card to retrieve
		 * @return Deck for the given index in the collection
		 */
		public function getDeck(index:int):FlexDeck
		{
			return _decks.source[index] as FlexDeck;
		}
		
		/**
		 * Retrieves a Card using instance unique id
		 * @param instanceId Card instance unique id
		 * @return Card for the given instance unique id
		 */		
		public function getDeckById(id:int):FlexDeck
		{
			var deck:FlexDeck = null;
			var index:int = getDeckIndexById(id);
			if (index != Shared.INVALID_INDEX)
			{
				deck = _decks.getItemAt(index) as FlexDeck;
			}
			return deck;
		}
		
		/**
		 * Adds a deck to the collection
		 * @param deck Deck to add
		 * 
		 */		
		public function addDeck(deck:FlexDeck):void
		{
			if (deck.id != DataObject.INVALID_ID && containsDeckById(deck.id)) throw new Error("Deck with the same id already contained into the collection!");
			if (containsDeckByName(deck.name)) throw new Error("Deck with the same name already contained into the collection!");
			_decks.addItem(deck);
			// Dispatch EVENT_DECK_LIST_CHANGED event
			dispatchDeckListChanged();
		}
		
		/**
		 * Removes a deck from the collection 
		 * @param deck Deck to remove
		 */		
		public function removeDeck(deck:FlexDeck):void
		{
			var index:int = getDeckIndexById(deck.id);
			if (index > Shared.INVALID_INDEX)
			{
				// Last deck check
				if (_decks.length == 1) throw new Error("The last deck cannot be removed from the Collection");
				// Check active deck
				if (deck.id == _activeDeckId) throw new Error("The active deck cannot be removed from the Collection");
				_decks.removeItemAt(index);
				// Dispatch EVENT_DECK_LIST_CHANGED event
				dispatchDeckListChanged();
			}
		}
		
		/**
		 * Clear all the collectionc decks 
		 */		
		public function clearDecks():void
		{
			_decks.removeAll();
			// Dispatch EVENT_DECK_LIST_CHANGED event
			dispatchDeckListChanged();
		}
		
		/**
		 * Checks if the collection is equals to the given
		 * @return True if the collection is equal to the given one
		 */		
		public function equals(collection:FlexCollection):Boolean
		{
			return	_activeDeckId == collection.activeDeck.id &&
					equalsCards(collection) &&
					equalsDecks(collection);
		}
		
		/**
		 *  Copies all cards and decks from a collection
		 */		
		public function copy(collection:FlexCollection):void
		{
			// Disables event dispatch
			dispatchEnabled = false;
			// Temporaty array
			var cards:ArrayList = new ArrayList();
			var decks:ArrayList = new ArrayList();
			// Copy each card
			for (var i:int = 0; i < collection.cardCount; i++)
			{
				cards.addItem( collection.getCard(i).clone() );
			}
			// Clears and set the complete action cards list
			clearCards();
			addAllCards(cards);
			// Copy each deck
			for (var j:int = 0; j < collection.decksCount; j++)
			{
				// Original deck
				var deck:FlexDeck = collection.getDeck(j);
				// Creates a new deck from the original
				var deck_copy:FlexDeck = createEmptyDeck(deck.id, deck.name);
				// Duplicates the deck using current collection cards but reading the source deck composition
				// deck_copy.mirror(deck);
				deck_copy.copy(deck);
				// Add deck to temporary list
				decks.addItem(deck_copy);
			}
			// Copy all the decks
			_decks.removeAll();
			_decks.addAll(decks);
			// Copy active deck
			_activeDeckId = collection.activeDeck.id;
			// Disables event dispatch
			dispatchEnabled = true;
			// Dispatch events
			dispatchDeckListChanged();
			dispatchCardListChanged();
			dispatchActionCardListChanged();
			dispatchPlayerCardListChanged();
			dispatchTrainingCardListChanged();
			dispatchActiveDeckChanged();
		}
		
		/**
		 * Creates a clone of the collection with all the cards and decks
		 * @return A new Collection with all the cards and decks
		 */		
		public function clone():FlexCollection
		{
			var collection:FlexCollection = new FlexCollection();
			collection.copy(this);
			return collection;
		}
		
		/**
		 * Clear all the collectionc cards and decks 
		 */		
		override public function clear():void
		{
			clearCards();
			clearDecks();
		}
		
		public function dump(source:Array = null, indentDepth:int = 0):Array
		{
			// return Dumper.dump(this, null, source, indentDepth);
			
			// Indent
			var indent:String = "";
			// Flag to add decoretors
			var decorate:Boolean = source == null || source.length == 0;
			
			// Decorator header
			if (decorate) source.push(Dumper.format(""));
			if (decorate) source.push(Dumper.format("Collection", true));
			
			// Indent
			indent = Dumper.indent(indentDepth);
			
			// Cards
			source.push(Dumper.format(indent + "cards [length:" + _cards.length + "]"));
			indent = Dumper.indent(indentDepth + 1);
			for (var i:int = 0; i < _cards.length; i++)
			{
				var card:Card = getCard(i);
				// source.push(Dumper.format(indent + "card " + card.name + "[type:" + card.type.name + ", id:" + card.instanceId + "]"));
				source.push(Dumper.format(indent + "[" + i + "] " + card.type.name + " " + card.name + " (id:" + card.instanceId + ")"));
			}
			
			// Indent
			indent = Dumper.indent(indentDepth);
			
			// Decks
			source.push(Dumper.format(indent + "decks [length:" + _decks.length + "]"));
			indent = Dumper.indent(indentDepth + 1);
			for (var j:int = 0; j < _decks.length; j++)
			{
				var deck:FlexDeck = getDeck(j);
				source.push(Dumper.format(indent + "[" + j + "] " + deck.name + " (cards:" + deck.actionCardsCount + ")"));
			}
			
			// Decorator footer
			if (decorate) source.push(Dumper.format("Collection", true));
			if (decorate) source.push(Dumper.format(""));
			
			return source;
		}
		
	}
}